package cn.dface.app.deploy.controller;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import cn.dface.app.deploy.entity.AppProcessRecord;
import cn.dface.app.deploy.entity.Application;
import cn.dface.app.deploy.entity.RecordResult;
import cn.dface.app.deploy.entity.ResponseResult;
import cn.dface.app.deploy.entity.StatusType;
import cn.dface.app.deploy.exception.DeployException;
import cn.dface.app.deploy.service.ApplicationService;

@RestController
@RequestMapping(value = "/apps")
public class ApplicationController {
	
	private final static String APPS_CONFIG_FLODER = "/WEB-INF/apps-repository/";
	private final static String APPS_RECORD_FLODER = "/WEB-INF/apps-record/";
	
	@Resource
	private ServletContext servletContext;
	@Autowired
	private ApplicationService appService;
	
	@RequestMapping(value = "/list")
	public List<Application> getAll() {
		return Arrays.stream(this.contructAppsConfigFile(null).listFiles())
					.map(file -> {
						return appService.getApplication(file);
					})
					.sorted((file1, file2) -> {
						return file1.getCreateDate().compareTo(file2.getCreateDate());
					})
					.collect(Collectors.toList());
	}
	
	@RequestMapping(value = "/name/{name}", method = RequestMethod.GET)
	public Application getOne(@PathVariable(value = "name") String name) {
		return Arrays.stream(this.contructAppsConfigFile(null).listFiles())
					  .filter(filed -> filed.getName().equals(name))
					  .findFirst()
					  .map(appConfigFile -> {
						  return appService.getApplication(appConfigFile);
					  }).orElse(null);
	}
	
	@RequestMapping(value ="/action/add", method = RequestMethod.POST)
	public ResponseResult addApp(
			@RequestParam(value = "name", required = true) String name,
			@RequestParam(value = "description", required = true) String description,
			@RequestParam(value = "url", required = true) String url) {
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		Application app = new Application();
		app.setName(name);
		app.setDescription(description);
		app.setUrl(url);
		app.setStatus(StatusType.UNPUBLISHED);
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(new Date());
		app.setCreateDate(calendar);
		File appConfigFile = this.contructAppsConfigFile(name);
		try {
			if (appConfigFile.exists()) {
				throw new DeployException("App named " + name + "already exist.");
			}
			appService.saveApplication(app, appConfigFile);
		} catch (DeployException e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/name/{name}", method = RequestMethod.DELETE)
	public ResponseResult removeApp(@PathVariable(value = "name") String name) {
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		File appConfigFile = this.contructAppsConfigFile(name);
		try {
			appService.deleteApplication(appConfigFile);
		} catch (DeployException e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/action/deploy/name/{name}")
	public ResponseResult deployApp(
			@PathVariable(value = "name") String name,
			HttpSession session) {
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		File appConfigFile = this.contructAppsConfigFile(name);
		File appRecordFile = this.contructAppsRecordFile(name);
		if (appConfigFile.exists()) {
			try {
				Application app = appService.getApplication(appConfigFile);
				ProcessBuilder pb = new ProcessBuilder(app.getUrl());
				Process proc = pb.redirectOutput(appRecordFile).start();
				session.setAttribute(name, new AppProcessRecord(proc, appConfigFile, appRecordFile, 0));
			} catch (IOException e) {
				result.setSucceeded(false);
				result.setMessage(e.getMessage());
				session.removeAttribute(name);
			}
		} else {
			result.setSucceeded(false);
			result.setMessage("App named " + name + "not found");
		}
		return result;
	}
	
	@RequestMapping(value = "/record/name/{name}")
	public RecordResult getDeployRecord(
			@PathVariable(value = "name") String name,
			HttpSession session) {
		RecordResult result = new RecordResult();
		result.setSucceeded(true);
		result.setFinished(false);
		AppProcessRecord procRecord = (AppProcessRecord) session.getAttribute(name);
		try (RandomAccessFile aFile = new RandomAccessFile(procRecord.getAppRecordFile(), "r")) {
			aFile.seek(procRecord.getPosition());
			long endPosition = Math.min(procRecord.getNextPosition(), aFile.length());
			StringBuilder retStr = new StringBuilder();
			while (aFile.getFilePointer() <= endPosition) {
				retStr.append(aFile.readLine() + "\n");
			}
			procRecord.setPosition(aFile.getFilePointer());
			result.setMessage(retStr.toString());
			if (!procRecord.getProc().isAlive() && aFile.getFilePointer() == aFile.length()) {
				result.setFinished(true);
				File appConfigFile = procRecord.getAppConfigFile();
				Application app = appService.getApplication(appConfigFile);
				app.setStatus(StatusType.PUBLISHED);
				appService.saveApplication(app, appConfigFile);
				session.removeAttribute(name);
			}
		} catch (IOException | DeployException e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}

	private File contructAppsRecordFile(String fileName) {
		return new File(servletContext.getRealPath(APPS_RECORD_FLODER), fileName + ".txt");
	}
	
	private File contructAppsConfigFile(String fileName) {
		return Optional.ofNullable(fileName)
					   .map(file -> {
						   return new File(servletContext.getRealPath(APPS_CONFIG_FLODER), fileName + ".properties");
					   })
					   .orElse(new File(servletContext.getRealPath(APPS_CONFIG_FLODER)));
	}

}
